/*
Created FishFunge because Befunge proved too fragile.
The odds of evolving a valid program through mutation in befunge are so small that
it might as well be considered impossible.
FishFunge is basically one dimensional Befunge
*/

package funge {
	import __AS3__.vec.Vector;
	
	import actors.Actor;
	
	import common.Tank;
	
	public class FishFunge implements IFunge {
		private var _actor:Actor;
		
		private var _cursor:Cursor;
		private var _program:Vector.<String>;
		private var _programWidth:int = 2048;
		private var _programHeight:int = 1;
		private var _curChar:String;
		private var _stack:Vector.<int>;
		
		private var _availableCommands:Vector.<String>;
		
		private var _tank:Tank;
		private var _destroyed:Boolean;
		
		public function FishFunge() {
			_cursor = new Cursor(_programWidth, _programHeight);
			
			_stack = new Vector.<int>();
			
			_availableCommands = new Vector.<String>();
			_availableCommands.push("0",
				"1",
				"2",
				"3",
				"4",
				"5",
				"6",
				"7",
				"8",
				"9",
				"+",
				"-",
				"*",
				"/",
				"%",
				"!",
				"'",
				":",
				"s",
				"$",
				"e",
				"r",
				"?",
				"j");
			
			_tank = Tank.instance;
			_destroyed = false;
		}
		
		public function update():void {
			//DEBUG:
			if (!actorSet)
				throw new Error("Befunge.actor must be set before updating");
			//DEBUG:
			if (!_actor)
				throw new Error("Befunge.actor cannot be null!");
			
			//run program until action is taken
			var actionTaken:Boolean = false;
//			var attemptCount:int = 100;
			for (var attemptCount:int = 100; attemptCount > 0 && !actionTaken; attemptCount --) {
				_curChar = _program[_cursor.y].charAt(_cursor.x);
				actionTaken = processCommand(_curChar);
				_cursor.update();
			}
			
			if (_cursor.y != 0)
				throw new Error("FishFunge.cursor.y must be 0");
		}
		
		private function processCommand(command:String):Boolean {
			var actionExecuted:Boolean = false;
			switch (command) {
				case "0":  //Push this number on the stack
					push(0);
					break;
				case "1":  //Push this number on the stack
					push(1);
					break;
				case "2":  //Push this number on the stack
					push(2);
					break;
				case "3":  //Push this number on the stack
					push(3);
					break;
				case "4":  //Push this number on the stack
					push(4);
					break;
				case "5":  //Push this number on the stack
					push(5);
					break;
				case "6":  //Push this number on the stack
					push(6);
					break;
				case "7":  //Push this number on the stack
					push(7);
					break;
				case "8":  //Push this number on the stack
					push(8);
					break;
				case "9":  //Push this number on the stack
					push(9);
					break; 
				case "+":  //Addition: Pop a and b, then push a+b
					push(pop() + pop());
					break;
				case "-":  //Subtraction: Pop a and b, then push a-b
					push(pop() - pop());
					break;
				case "*":  //Multiplication: Pop a and b, then push a*b
					push(pop() * pop());
					break;
				case "/":  //Integer division: Pop a and b, then push a/b
					push(pop() / pop(1));
					break;
				case "%":  //Modulo: Pop a and b, then push the remainder of the integer division of b/a
					push(pop() % pop(1));
					break;
				case "!":  //Logical NOT: Pop a value. If the value is zero, push 1; otherwise, push zero
					if (!pop())
						push(0);
					else
						push(1);
					break;
				case "'":  //Greater than: Pop a and b, then push 1 if b>a, otherwise zero
					if (pop() > pop())
						push(1);
					else
						push(0);
					break;
				case ":":  //Duplicate value on top of the stack
					var dupe:int = pop();
					push(dupe);
					push(dupe);
					break;
				case "s":  //Swap two values on top of the stack
					var swapOne:int = pop();
					var swapTwo:int = pop();
					push(swapOne);
					push(swapTwo);
					break;
				case "$":  //Pop value from the stack
					pop();
					break;
				case "e":  //Execute command: Pop value from stack and execute correspondin command
					var commands:Vector.<String> = _actor.availableCommands;
					var commandIndex:int = pop(0, commands.length - 1);
					_actor[commands[commandIndex]]();
					actionExecuted = true;
					break;
				case "r":  //Recall: Pop off stack until end value is reached.  Push end value onto stack
					var endValue:Boolean = false;
					var memoryIndex:int = pop(0, _actor.memory.length - 1);
					var memoryValue:Object = _actor.memory[memoryIndex];
					while (!endValue) {
						if (memoryValue.hasOwnProperty("observableProps")) {
							memoryIndex = pop(0, memoryValue.observableProps.length - 1)
							memoryValue = Actor(memoryValue).observableProps[memoryIndex];
						}
						else if (memoryValue is Vector.<*> || memoryValue is Array) {
							memoryIndex = pop(0, memoryValue.length - 1);
							if (memoryIndex < memoryValue.length)
								memoryValue = memoryValue[memoryIndex];
							else
								memoryValue = 0;
						}
						else {
							endValue = true;
						}
					}
					
					//DEBUG:
					if (!(memoryValue is int) && !(memoryValue is Number) && !(memoryValue is Boolean))
						throw new Error("Befunge.processCommand.r ended with value that is not a number.");
					
					push(int(memoryValue));
					break;
				case "?":
					if (pop() < pop())
						_cursor.jump(pop(), 0);
					break;
				case "j":
					_cursor.jump(pop(), 0);
					break;
				case " ":  //NOP: do nothing for white space
					break;
				default:
					throw new Error("Befunge.processCommand has encountered invalid command: " + command + "at: " + _cursor.x + "," + _cursor.y);
			}
			
			return actionExecuted;
		}
		
		private function push(value:int):void {
			_stack.push(value);
		}
		
		//values wrap if greater or less than min/max
		private function pop(min:int = int.MIN_VALUE, max:int = int.MAX_VALUE):int {
			var value:int = _stack.pop();
			if (value < min) {
				var dist:int = Math.abs(value) % ((max - min) + 1);
				value = max - dist + 1;// (min - value);
			}
			if (value > max)
				value = value % ((max - min) + 1) + min;
			
			return value;
		}
		
		public function clone():IFunge {
			var newFunge:FishFunge = new FishFunge();
			newFunge.program = copyProgram();
			
			return newFunge;
		}
		
		public function copyProgram():Vector.<String> {
			var programCopy:Vector.<String> = new Vector.<String>();
			for (var i:int = 0; i < 1; i ++) {
				var commands:String = _program[i];
				var commandsCopy:String = "";
				for (var j:int = 0; j < _programWidth; j ++) {
					commandsCopy += commands.charAt(j);
				}
				programCopy.push(commandsCopy);
			}
			
			return programCopy;		
		}
		
		public function mutate():void {
			for (var i:int = 0; i < 1; i ++) {
				var commands:String = _program[i];
				var mutatedCommands:String = "";
				for (var j:int = 0; j < _programWidth; j ++) {
					if (Math.random() < _tank.mutationChance) //mutation!
						mutatedCommands += availableCommands[int(Math.random() * availableCommands.length)];
					else {
						mutatedCommands += commands.charAt(j);
					}
				}
				_program[i] = mutatedCommands;
			}
		}
		
		public function toString():String {
			var output:String = "";
			for (var i:int = 0; i < _program.length; i ++) {
				output += _program[i] += "\n";
			}
			
			return output;
		}
		
		public function blend(otherProgram:IFunge):IFunge {
			if (!(otherProgram is FishFunge))
				throw new Error("FishFunge.blend only works with FishFunge object as input.");
			
			var blendedProgram:Vector.<String> = new Vector.<String>();
			for (var i:int = 0; i < 25; i ++) {
				blendedProgram.push("");
				for (var j:int = 0; j < _programWidth; j ++) {
					if (Math.random() < .5)
						blendedProgram[i] += _program[i].charAt(j);
					else
						blendedProgram[i] += otherProgram.program[i].charAt(j);
				}
			}
			
			var blendedBefunge:Befunge = new Befunge();
			blendedBefunge.program = blendedProgram;
			
			blendedBefunge.mutate();
			
			return blendedBefunge;
		}
		
		
		public function randomize():void {
			_program = new Vector.<String>();
//			trace("BEGIN!!!!");
			for (var i:int = 0; i < 1; i ++) {
				_program[i] = "";
				for (var j:int = 0; j < _programWidth; j ++) {
					var randCommand:String;
					if (Math.random() < 0.5)
						randCommand = " ";
					else if (Math.random() < .25)
						randCommand = "e";
					else
						randCommand = availableCommands[int(Math.random() * availableCommands.length)];
					_program[i] += randCommand;
				}
//				trace(_program[i]);
			}
//			trace("END!!!!");
		}
		
		public function template():void {
			_program = new Vector.<String>();
			_program.push("0e0e0e1e0e1e0e1e0e1e0e1e0e0e5e5e2e0e2e0e2e0e3e3e4e4e5e5e0j");
			while (_program[0].length < _programWidth) {
				var randCommand:String;
				if (Math.random() < 0.5)
					randCommand = " ";
				else if (Math.random() < .25)
					randCommand = "e";
				else
					randCommand = availableCommands[int(Math.random() * availableCommands.length)];
				_program[0] += randCommand;
			}
			trace(_program);
		}
		
		//simple getters/setters
		public function get availableCommands():Vector.<String>		{return _availableCommands;}
		public function get program():Vector.<String>				{return _program;}
		public function get destroyed():Boolean						{return _destroyed;}
		
		public function set program(commands:Vector.<String>):void	{_program = commands;}
		
		//complex getters/setters
		//DEBUG:
		private var actorSet:Boolean = false;
		public function set actor(actorIn:Actor):void {
			actorSet = true;
			_actor = actorIn;

			//DEBUG:
			if (!_actor)
				throw new Error("Befunge.actor cannot be null!");
		}
		
		
		public function destroy():void {
			if (_destroyed)
				return;
			_destroyed = true;
			
			_actor = null;
			_cursor = null;
			_program = null;
			_curChar = null;
			_stack = null;
		}
	}
}